Passed
Pull Request — master (#19)
by Muhammad Dyas
01:30
created

ActionHandler.startPoll   A

Complexity

Conditions 4

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 34
rs 9.28
c 0
b 0
f 0
cc 4
1
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
2
import BaseHandler from './BaseHandler';
3
import NewPollFormCard from '../cards/NewPollFormCard';
4
import {addOptionToState, getConfigFromInput, getStateFromCard} from '../helpers/state';
5
import {callMessageApi} from '../helpers/api';
6
import {createDialogActionResponse, createStatusActionResponse} from '../helpers/response';
7
import PollCard from '../cards/PollCard';
8
import {ClosableType, MessageDialogConfig, PollFormInputs, PollState, Voter} from '../helpers/interfaces';
9
import AddOptionFormCard from '../cards/AddOptionFormCard';
10
import {saveVotes} from '../helpers/vote';
11
import {PROHIBITED_ICON_URL} from '../config/default';
12
import ClosePollFormCard from '../cards/ClosePollFormCard';
13
import MessageDialogCard from '../cards/MessageDialogCard';
14
15
/*
16
This list methods are used in the poll chat message
17
 */
18
interface PollAction {
19
  saveOption(): Promise<chatV1.Schema$Message>;
20
21
  getEventPollState(): PollState;
22
}
23
24
export default class ActionHandler extends BaseHandler implements PollAction {
25
  async process(): Promise<chatV1.Schema$Message> {
26
    const action = this.event.common?.invokedFunction;
27
    switch (action) {
28
      case 'start_poll':
29
        return await this.startPoll();
30
      case 'vote':
31
        return this.recordVote();
32
      case 'add_option_form':
33
        return this.addOptionForm();
34
      case 'add_option':
35
        return await this.saveOption();
36
      case 'show_form':
37
        const pollForm = new NewPollFormCard({topic: '', choices: []}, this.getUserTimezone()).create();
38
        return createDialogActionResponse(pollForm);
39
      case 'new_poll_on_change':
40
        return this.newPollOnChange();
41
      case 'close_poll_form':
42
        return this.closePollForm();
43
      case 'close_poll':
44
        return await this.closePoll();
45
      default:
46
        return createStatusActionResponse('Unknown action!', 'UNKNOWN');
47
    }
48
  }
49
50
  /**
51
   * Handle the custom start_poll action.
52
   *
53
   * @returns {object} Response to send back to Chat
54
   */
55
  async startPoll() {
56
    // Get the form values
57
    const formValues: PollFormInputs = this.event.common!.formInputs! as PollFormInputs;
58
59
    const config = getConfigFromInput(formValues);
60
61
    if (!config.topic || config.choices.length === 0) {
62
      // Incomplete form submitted, rerender
63
      const dialog = new NewPollFormCard(config, this.getUserTimezone()).create();
64
      return createDialogActionResponse(dialog);
65
    }
66
    const pollCard = new PollCard({
67
      author: this.event.user,
68
      ...config,
69
    }).createCardWithId();
70
    // Valid configuration, make the voting card to display in the space
71
    const message = {
72
      cardsV2: [pollCard],
73
    };
74
    const request = {
75
      parent: this.event.space?.name,
76
      requestBody: message,
77
    };
78
    const apiResponse = await callMessageApi('create', request);
79
    if (apiResponse) {
80
      return createStatusActionResponse('Poll started.', 'OK');
81
    } else {
82
      return createStatusActionResponse('Failed to start poll.', 'UNKNOWN');
83
    }
84
  }
85
86
  /**
87
   * Handle the custom vote action. Updates the state to record
88
   * the user's vote then rerenders the card.
89
   *
90
   * @returns {object} Response to send back to Chat
91
   */
92
  recordVote() {
93
    const parameters = this.event.common?.parameters;
94
    if (!(parameters?.['index'])) {
95
      throw new Error('Index Out of Bounds');
96
    }
97
    const choice = parseInt(parameters['index']);
98
    const userId = this.event.user?.name ?? '';
99
    const userName = this.event.user?.displayName ?? '';
100
    const voter: Voter = {uid: userId, name: userName};
101
    const state = this.getEventPollState();
102
103
    // Add or update the user's selected option
104
    state.votes = saveVotes(choice, voter, state.votes!, state.anon);
105
    const card = new PollCard(state);
106
    return {
107
      thread: this.event.message?.thread,
108
      actionResponse: {
109
        type: 'UPDATE_MESSAGE',
110
      },
111
      cardsV2: [card.createCardWithId()],
112
    };
113
  }
114
115
  /**
116
   * Opens and starts a dialog that allows users to add details about a contact.
117
   *
118
   * @returns {object} open a dialog.
119
   */
120
  addOptionForm() {
121
    const state = this.getEventPollState();
122
    const dialog = new AddOptionFormCard(state).create();
123
    return createDialogActionResponse(dialog);
124
  };
125
126
  /**
127
   * Handle add new option input to the poll state
128
   * the user's vote then rerenders the card.
129
   *
130
   * @returns {object} Response to send back to Chat
131
   */
132
  async saveOption(): Promise<chatV1.Schema$Message> {
133
    const userName = this.event.user?.displayName ?? '';
134
    const state = this.getEventPollState();
135
    const formValues = this.event.common?.formInputs;
136
    const optionValue = formValues?.['value']?.stringInputs?.value?.[0]?.trim() || '';
137
    addOptionToState(optionValue, state, userName);
138
139
    const cardMessage = new PollCard(state).createMessage();
140
141
    const request = {
142
      name: this.event.message!.name,
143
      requestBody: cardMessage,
144
      updateMask: 'cardsV2',
145
    };
146
    const apiResponse = await callMessageApi('update', request);
147
    if (apiResponse) {
148
      return createStatusActionResponse('Option is added', 'OK');
149
    } else {
150
      return createStatusActionResponse('Failed to add option.', 'UNKNOWN');
151
    }
152
  }
153
154
  getEventPollState(): PollState {
155
    const stateJson = getStateFromCard(this.event);
156
    if (!stateJson) {
157
      throw new ReferenceError('no valid state in the event');
158
    }
159
    return JSON.parse(stateJson);
160
  }
161
162
  async closePoll(): Promise<chatV1.Schema$Message> {
163
    const state = this.getEventPollState();
164
    state.closedTime = Date.now();
165
    const cardMessage = new PollCard(state).createMessage();
166
    const request = {
167
      name: this.event.message!.name,
168
      requestBody: cardMessage,
169
      updateMask: 'cardsV2',
170
    };
171
    const apiResponse = await callMessageApi('update', request);
172
    if (apiResponse) {
173
      return createStatusActionResponse('Poll is closed', 'OK');
174
    } else {
175
      return createStatusActionResponse('Failed to close poll.', 'UNKNOWN');
176
    }
177
  }
178
179
  closePollForm() {
180
    const state = this.getEventPollState();
181
    if (state.type === ClosableType.CLOSEABLE_BY_ANYONE || state.author!.name === this.event.user?.name) {
182
      return createDialogActionResponse(new ClosePollFormCard().create());
183
    }
184
185
    const dialogConfig: MessageDialogConfig = {
186
      title: 'Sorry, you can not close this poll',
187
      message: `The poll setting restricts the ability to close the poll to only the creator(${state.author!.displayName}).`,
188
      imageUrl: PROHIBITED_ICON_URL,
189
    };
190
    return createDialogActionResponse(new MessageDialogCard(dialogConfig).create());
191
  }
192
193
  newPollOnChange() {
194
    const formValues: PollFormInputs = this.event.common!.formInputs! as PollFormInputs;
195
    const config = getConfigFromInput(formValues);
196
    return createDialogActionResponse(new NewPollFormCard(config, this.getUserTimezone()).create());
197
  }
198
}
199